home *** CD-ROM | disk | FTP | other *** search
/ FishMarket 1.0 / FishMarket v1.0.iso / fishies / 001-025 / disk_006 / mult / mult.c < prev    next >
C/C++ Source or Header  |  1992-05-06  |  10KB  |  425 lines

  1. /*
  2.  * mult.c
  3.  * dennis bednar 08 08 85  Original creation.
  4.  * dennis bednar 01 09 86 Added -F flag, added debug flag.
  5.  * report bugs/suggestions etc. to dennis@rlgvax.uucp
  6.  *
  7.  * mult read the input (stdin or file(s)), comparing adjacent lines.
  8.  * In the normal case, the second, and succeeding copies of repeated
  9.  * lines are output to stdout.
  10.  * Note that repeated lines must be adjacent, see sort(1).
  11.  * This tool is sort of the opposite of uniq.
  12.  *
  13.  * -fn = use field number n in each line for the comparison, n = 1 = first.
  14.  * Note - in the 2 lines " abc    def" and "abc  def", "abc" is field # 1,
  15.  * and "def" is field number 2, multiple white space chars are field separators.
  16.  *
  17.  * -a = output 1st of multiple occurences
  18.  * Note - this flag is very useful in conjunction with -fn flag.
  19.  * Example: trying to find all include files which are in multiple dirs:
  20.  * with input sorted by 1st column:
  21. stdio.h        /usr/include
  22. stdio.h        /tmp/junk
  23.  * we would use both "-f1 -a" flags to print only those lines in which
  24.  * include files were in more than one directory, but not outputing
  25.  * those lines in which include files were in only one directory.
  26.  *
  27.  */
  28.  
  29. #include <stdio.h>
  30.  
  31. char    *cmd;    /* in case of error */
  32. int    aflag;    /* 1 if -a */
  33. int    dflag;    /* 1 if -d debug */
  34. int    fflag;    /* 1 if -fn */
  35. char    Fflag = '\0';    /* field separator, 0 = white space, else one char */
  36. int    fieldnum;    /* value of # in -f# option, valid if fflag == 1 */
  37.  
  38. extern    char    *u_errmesg();
  39.  
  40. /* f/w ref */
  41. char    *find_field();
  42.  
  43. main(argc, argv)
  44.     int    argc;
  45.     char    **argv;
  46. {
  47.     int    i;
  48.     FILE    *infp;
  49.  
  50.  
  51.     cmd = argv[0];
  52.  
  53.     /* loop thru args, stopping at end of args or first file name */
  54.     for (i = 1; i < argc; ++i)
  55.         {
  56.         if (argv[i][0] != '-')
  57.             break;    /* found first non-option, ie 1st filename */
  58.         if (strcmp(argv[i], "-a") == 0)
  59.             {
  60.             aflag=1;
  61.             continue;
  62.             }
  63.  
  64.         /* get debug flag */
  65.         if (strcmp(argv[i], "-d") == 0)
  66.             {
  67.             ++dflag;    /* enable debugging */
  68.             printf("Debugging on\n");
  69.             continue;    /* goto next argument */
  70.             }
  71.  
  72.         /* get field number */
  73.         if (strncmp(argv[i], "-f", 2) == 0)
  74.             {
  75.             if (fflag)
  76.                 goto usage;    /* only one -fn allowed */
  77.             fflag = 1;
  78.             if (argv[i][2] == '\0')
  79.                 goto usage;
  80.             fieldnum = atoi(argv[i]+2);
  81.             if (fieldnum <= 0)
  82.                 {
  83.                 fprintf(stderr, "%s: 'field number' must be positive\n", cmd);
  84.                 goto usage;
  85.                 }
  86.             continue;
  87.             }
  88.  
  89.         /* get field separator character */
  90.         if (strncmp(argv[i], "-F", 2) == 0)
  91.             {
  92.             if (Fflag)
  93.                 goto usage;    /* only one -Fc allowed */
  94.             Fflag = argv[i][2];    /* save field separator char */
  95.             if (argv[i][2] == '\0')
  96.                 goto usage;    /* no field separator */
  97.             continue;
  98.             }
  99. usage:
  100.         fprintf(stderr, "usage: %s [-a] [-d] [-fn] [-Fc] [file ...]\n", cmd);
  101.         fprintf(stderr, "        outputs 2nd, 3rd, ... of multiple lines\n");
  102.         fprintf(stderr, "        -a = also output 1st one of multiple lines\n");
  103.         fprintf(stderr, "        -d = debug\n");
  104.         fprintf(stderr, "        -fn = use field number n to compare instead of line, 1=1st field,\n");
  105.         fprintf(stderr, "              with white space as field separator\n");
  106.         fprintf(stderr, "        -Fc = means use character 'c' as the field separator\n");
  107.         exit(1);
  108.         }
  109.  
  110.     if (i == argc)        /* no file names given */
  111.         mult(stdin);    /* so read from stdin */
  112.     else
  113.         for ( ;i < argc; ++i)    /* use given file names */
  114.             {
  115.             infp = fopen( argv[i], "r");
  116.             if (infp == (FILE *)NULL)
  117.                 {
  118.                 fprintf(stderr, "%s: cant open %s: %s\n", cmd, argv[i], u_errmesg());
  119.                 continue;
  120.                 }
  121.             mult( infp );
  122.             fclose(infp);
  123.             }
  124. }
  125.  
  126. /* save the lines here */
  127.  
  128. struct    t_line
  129.     {
  130. #define LINESIZE 2048
  131.     char    linebuf [ LINESIZE ];
  132.     } line [2];
  133.  
  134. /* use index for faster copy!! */
  135. int    old = 0;        /* index of old line */
  136. int    new = 1;         /* index of new line */
  137.  
  138. /* state flag to help decide actions based on state transitions */
  139. #define S_START        0
  140. #define S_UNIQLINE    1    /* saw 1st line or new one different than the old */
  141. #define S_MULTLINE    2    /* saw new line which is same as the first */
  142. int    state = S_START;
  143.  
  144. /* address of the first character in each line buffer */
  145. #define OLDLINE line[old].linebuf
  146. #define NEWLINE line[new].linebuf
  147.  
  148. mult( infp )
  149.     FILE    *infp;
  150. {
  151.     int    isdiff;        /* 1 iff old line != new line */
  152.  
  153.     /* keep reading lines until eof */
  154.     while (1)
  155.         {
  156.  
  157.         /* this is not very efficient, but its the only way
  158.          * I could think of, otherwise main() gets ugly.
  159.          */
  160.  
  161.         /* read in next line from input */
  162.         if (fgets(NEWLINE, LINESIZE, infp) == NULL)
  163.             return;        /* EOF - no state transition */
  164.  
  165.         stripnl(NEWLINE);    /* remove ending newline from string */
  166.  
  167.         /* first time mult() is called, we must save the 1st line
  168.          * read as the 'oldline' for comparing against future 'newline's
  169.          */
  170.         if (state == S_START)
  171.             {
  172.             swapline();    /* copy new line to old line */
  173.             state = S_UNIQLINE;
  174.             continue;        /* get next line */
  175.             }
  176.  
  177.         /* compare the old vs new line, since needed in both states */
  178.         /* compute it once to make code more efficient */
  179.  
  180. #define DIFF strcmp
  181.         if (fflag)        /* compare by field ? */
  182.             /* yes, pass the global fieldnum so that same_field()
  183.              * is kept modular, and reusable in other applications
  184.              */
  185.             isdiff = !same_field(OLDLINE, NEWLINE, fieldnum);
  186.         else            /* no compare entire line */
  187.             isdiff = (DIFF(OLDLINE, NEWLINE));
  188.  
  189.  
  190.         if (state == S_UNIQLINE)
  191.             {
  192.             if (isdiff)
  193.                 {
  194.                 swapline();
  195.                 /* stay in same state */
  196.                 }
  197.             else
  198.                 {
  199.                 if (aflag)
  200.                     printf("%s\n", OLDLINE);
  201.                 printf("%s\n", NEWLINE);
  202.                 swapline();
  203.                 state = S_MULTLINE;
  204.                 }
  205.             }
  206.         else if (state == S_MULTLINE)
  207.             {
  208.             if (isdiff)
  209.                 {
  210.                 swapline();
  211.                 state = S_UNIQLINE;
  212.                 }
  213.             else
  214.                 {
  215.                 printf("%s\n", NEWLINE);
  216.                 swapline();
  217.                 /* stay in multiple line state */
  218.                 }
  219.             }
  220.         }
  221. }
  222.  
  223.  
  224. /*
  225.  * swap old line with new line
  226.  * Called after read into new line, so that effect is same as copying
  227.  * newline to old line, and discarding newline.
  228.  */
  229. swapline()
  230. {
  231.     register    int    t;    /* temp */
  232.  
  233.     t = old;
  234.     old = new;
  235.     new = t;
  236. }
  237.  
  238.  
  239. /*
  240.  * return 1 iff field number 'fieldnum' (1=1st) is same in
  241.  * old line vs. new line.
  242.  */
  243. same_field(oldline, newline, fieldnum)
  244.     char    *oldline,
  245.         *newline;
  246.     int    fieldnum;
  247.  
  248. {
  249.     char    *op,        /* old field ptr */
  250.         *np;        /* new field ptr */
  251.  
  252.     op = find_field(oldline, fieldnum);
  253.     if (dflag)        /* debug */
  254.         {
  255.         /* dump out the fields being compared */
  256.         char *cp;
  257.         printf("Old field %d = <", fieldnum);
  258.         if (*op == '\0')    /* past last field in line */
  259.             printf("UNDEF");
  260.         else
  261.             for (cp = op; *cp && !field_dlm(*cp); ++cp)
  262.                 printf("%c", *cp);
  263.         printf("> ");
  264.         printf("Old line = <%s>\n", oldline);
  265.         }
  266.     np = find_field(newline, fieldnum);
  267.     if (dflag)
  268.         {
  269.         char *cp;
  270.         printf("New field %d = <", fieldnum);
  271.         if (*np == '\0')    /* past last field in line */
  272.             printf("UNDEF");
  273.         else
  274.             for (cp = np; *cp && !field_dlm(*cp); ++cp)
  275.                 printf("%c", *cp);
  276.         printf("> ");
  277.         printf("New line = <%s>\n", newline);
  278.         }
  279.  
  280.     if (*op == '\0' || *np == '\0')    /* is either field non-existent ? */
  281.         return 0;        /* assume failed to match */
  282.  
  283.     /* compare fields until either one ends */
  284.     /* a field ends with either a non-zero delimiter or a '\0' char */
  285.     for ( ; *op || *np; ++op, ++np)    /* both strings not exhausted */
  286.         {
  287.         /* Important: Please note that field_dlm() checks for '\0' also */
  288.         if (field_dlm(*op) && field_dlm(*np))    /* both reached end */
  289.             return 1;    /* hit end of field */
  290.         /* next cmp will handle case when only one field delimiter */
  291.         if (*op != *np)        /* cmp both chars in the field */
  292.             return 0;    /* failed to match */
  293.         /* both matched, keep going */
  294.         }
  295.  
  296.     /* both strings hit EOS, so matched that way */
  297.     return 1;            /* matched */
  298. }
  299.  
  300.  
  301. /*
  302.  * return 1 iff a field delimiter such as white space or end of string
  303.  * a null char is always a field delimiter, because the null replaces
  304.  * the last newline after the line has been read in.
  305.  */
  306. field_dlm(c)
  307.     char    c;
  308. {
  309.     if (c == '\0')            /* is it a null at End of String ? */
  310.         return 1;        /* yes, return true, because a delimiter */
  311.     if (Fflag)            /* field separator defined ? */
  312.         return (c == Fflag);    /* yes, see if it matches the one given */
  313.     else                /* no, must check for white space */
  314.         return (c == ' ' || c == '\t' || c == '\n');
  315. }
  316.  
  317.  
  318. /*
  319.  * return ptr to 'num' nth field, 1 = first field in the buffer.
  320.  * return ptr to '\0' if ask for a field not present
  321.  */
  322. char *
  323. find_field (line, num)
  324.     char    *line;
  325.     int    num;
  326. {
  327.     char    *cp;    /* ptr to return */
  328.  
  329.     /* must ask for valid field number */
  330.     if (num < 1)
  331.         return (line+strlen(line));    /* '\0' */
  332.  
  333.     /* beginning of line */
  334.     cp = line;
  335.  
  336.     while ( num-- > 0)
  337.         {
  338.         /* skip poss leading white space */
  339.  
  340. #define iswhite(c) ( (((c) & 0xff) == '\t') || (((c) & 0xff) == ' ') )
  341.  
  342.         if (Fflag)            /* using non-white field delimiter */
  343.             ;            /* so first char is field 1 */
  344.         else                /* using white space field dlm */
  345.             {
  346.             while (*cp && iswhite(*cp))
  347.                 ++cp;
  348.             /* cp is now at '\0' EOS or 1st non-white */
  349.             }
  350.  
  351.  
  352.         /* stop if at beginning of desired field */
  353.         if (num <= 0)
  354.             break;
  355.  
  356.         /* else skip over this symbol to either End of String
  357.          * or next white space , or next delimiter.
  358.          */
  359.         /* now find the last char of this symbol */
  360.         if (Fflag)        /* non-white field delimiter */
  361.             {
  362.             while (*cp && !field_dlm(*cp))
  363.                 ++cp;
  364.             /* hit '\0' EOS or field delimiter */
  365.             if (*cp)    /* fld */
  366.                 ++cp;    /* so make it point to begin of next field */
  367.             else
  368.                 ;    /* don't go past end of string !!! */
  369.             }
  370.         else            /* white space delimiter */
  371.             {
  372.             while (*cp && !iswhite(*cp))
  373.                 ++cp;
  374.             /* cp points to EOS or next white space char */
  375.             }
  376.         }
  377.  
  378.     return cp;
  379. }
  380.  
  381.  
  382. /*
  383.  * strip ending new line from string returned by fgets.
  384.  * If not present as last char , then line too long.
  385.  */
  386. stripnl(s)
  387.     char    *s;
  388. {
  389.     char    *cp;
  390.  
  391.     cp = &s[strlen(s) - 1];    /* ptr to last char of string */
  392.     if (*cp == '\n')    /* is last char a new line */
  393.         *cp = '\0';    /* yes, remove it */
  394.     else
  395.         {
  396.         fprintf(stderr, "%s: error line <%s>... was too long\n", cmd, s);
  397.         exit(1);
  398.         }
  399. }
  400.  
  401. /*
  402.  * return the error message string using errno
  403.  * More flexibility than perror(3).
  404.  */
  405. char *
  406. u_errmesg()
  407. {
  408. #ifdef unix
  409.     extern    int    errno;
  410.     extern    int    sys_nerr;
  411.     extern    char    *sys_errlist[];
  412.     static    char    buffer[50];
  413.  
  414.     if (errno < 0 || errno >= sys_nerr)
  415.         {
  416.         sprintf( buffer, "errno %d undefined (%d=max)", errno, sys_nerr);
  417.         return(buffer);
  418.         }
  419.  
  420.     return( sys_errlist[errno] );
  421. #else
  422.     return ("unknown error");
  423. #endif
  424. }
  425.